package com.songxh.application.impl;

import com.alibaba.fastjson.JSONObject;
import com.songxh.application.api.LoginService;
import com.songxh.application.dto.LoginInfoDTO;
import com.songxh.application.dto.SysRoleDTO;
import com.songxh.application.dto.SysUserDTO;
import com.songxh.application.dto.UserLoginParamsDTO;
import com.songxh.core.entity.UserContext;
import com.songxh.core.enums.BizCodeEnum;
import com.songxh.core.exception.BizException;
import com.songxh.core.modle.RoleInfoDTO;
import com.songxh.core.shared.constant.RedisConsts;
import com.songxh.core.utils.DateUtils;
import com.songxh.core.utils.JWTUtils;
import com.songxh.core.utils.LoginUtils;
import com.songxh.core.utils.RedisUtils;
import com.songxh.domain.service.api.SysRoleService;
import com.songxh.domain.service.api.SysUserService;
import com.songxh.infrastructure.db.pojo.SysUser;
import com.songxh.shared.enums.PassWordEnum;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * @author songxh
 * @create 2022-12-20-20:10
 */
@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private SysRoleService sysRoleService;


    @Transactional(rollbackFor = Exception.class)
    @Override
    public LoginInfoDTO loginByAccount(UserLoginParamsDTO userLoginParamsDTO) {
        //根据账号查找用户信息
        SysUser sysUser = sysUserService.lambdaQuery()
                .eq(SysUser::getAccount, userLoginParamsDTO.getAccount())
                .one();
        if (sysUser == null) {
            throw new BizException(BizCodeEnum.USER_INFO_NOT_EXIST.getMsg());
        }

        //校验密码
        String secretPassword = DigestUtils.md5Hex(userLoginParamsDTO.getPassword() + PassWordEnum.PASS_WORD_ENUM.getDesc());//根据加密盐获取到加密后的密码
        if (!sysUser.getPassword().equals(secretPassword)) {
            throw new BizException(BizCodeEnum.ERROR_PASSWORD.getMsg());
        }

        LoginInfoDTO loginInfo = getLoginInfo(sysUser);

        //刷新随后登录时间
        String now = DateUtils.format(new Date(), DateUtils.DEFAULT_DATE_TIME_FORMAT);
        JWTUtils.refreshLoinTime(loginInfo.getSysUser().getAccount(), loginInfo.getSysUser().getId(), now);
        boolean update = sysUserService.lambdaUpdate()
                .eq(SysUser::getId, loginInfo.getSysUser().getId())
                .set(SysUser::getLastLogin, now)
                .update();
        if (!update){
            throw new BizException("修改最后登录时间出错");
        }

        return loginInfo;
    }

    /**
     * 登出
     *
     * @return
     */
    @Override
    public Boolean logout() {
        UserContext userContext = LoginUtils.get();
        if (userContext == null) {
            throw new BizException(BizCodeEnum.LOGOUT_ERROR_USER_NOT_EXIST.getMsg());
        }
        String account = userContext.getAccount();
        Long userId = userContext.getId();
        //清除token与用户信息
        String accessTokenKey = String.format(RedisConsts.S_Token_ACCESS_KEY, account, userId);
        RedisUtils.deleteByKey(accessTokenKey);
        String userInfoKey = String.format(RedisConsts.S_USER_INFO_KEY, accessTokenKey, userId);
        RedisUtils.deleteByKey(userInfoKey);
        return Boolean.TRUE;
    }

    /**
     * 获取登录用户返回的信息
     *
     * @param sysUser
     * @return
     */
    private LoginInfoDTO getLoginInfo(SysUser sysUser) {
        //检查缓存中是否存在该用户的数据
        LoginInfoDTO loginInfoFromRedis = getLoginInfoFromRedis(sysUser.getAccount(), sysUser.getId());
        if (loginInfoFromRedis != null) {
            return loginInfoFromRedis;
        }
        LoginInfoDTO infoDTO = new LoginInfoDTO();
        //获取token，并存入缓存
        //这两个参数的目的是再之后偶检查token时从redis中获取token
        JSONObject tokenData = new JSONObject();
        tokenData.put("account", sysUser.getAccount());
        tokenData.put("userId", sysUser.getId());
        //token
        String accessToken = JWTUtils.creatJWTToken(JSONObject.parseObject(tokenData.toJSONString(), Map.class), UUID.randomUUID().toString());
        //redis 的 key
        String accessTokenKey = String.format(RedisConsts.S_Token_ACCESS_KEY, sysUser.getAccount(), sysUser.getId());
        RedisUtils.set(accessTokenKey, accessToken, RedisConsts.DEFAULT_EXPIRE_TIME_SECONDS);

        //设置登录回传信息
        infoDTO.setAccessToken(accessToken);
        SysUserDTO userDTO = new SysUserDTO();
        BeanUtils.copyProperties(sysUser, userDTO);
        infoDTO.setSysUser(userDTO);
        List<SysRoleDTO> sysRoleDTOS = sysRoleService.getRoleByUserId(sysUser.getId());
        infoDTO.setSysRoles(sysRoleDTOS);
        //保存数据至redis
        saveLoginInfoToRedis(userDTO, sysRoleDTOS);

        return infoDTO;
    }

    /**
     * 将数据存入redis
     *
     * @param sysUserDTO
     * @param sysRoleDTO
     */
    private void saveLoginInfoToRedis(SysUserDTO sysUserDTO, List<SysRoleDTO> sysRoleDTO) {
        UserContext userContext = new UserContext();
        BeanUtils.copyProperties(sysUserDTO, userContext);
        List<RoleInfoDTO> roleInfoDTO = new ArrayList<>();
        BeanUtils.copyProperties(sysRoleDTO, roleInfoDTO);
        userContext.setRoleInfoDTOS(roleInfoDTO);
        String userInfoKey = String.format(RedisConsts.S_USER_INFO_KEY, sysUserDTO.getAccount(), sysUserDTO.getId());
        RedisUtils.set(userInfoKey, userContext);
    }

    /**
     * 从redis中获取数据
     *
     * @param account
     * @param userId
     * @return
     */
    private LoginInfoDTO getLoginInfoFromRedis(String account, Long userId) {
        String userInfoKey = String.format(RedisConsts.S_USER_INFO_KEY, account, userId);
        UserContext userContext = RedisUtils.get(userInfoKey, UserContext.class);
        if (userContext == null) {
            return null;
        }
        LoginInfoDTO loginInfoDTO = new LoginInfoDTO();
        List<RoleInfoDTO> roleInfoDTOS = userContext.getRoleInfoDTOS();
        SysUserDTO sysUserDTO = new SysUserDTO();
        BeanUtils.copyProperties(userContext, sysUserDTO);
        List<SysRoleDTO> roleInfoDTO = new ArrayList<>();
        BeanUtils.copyProperties(roleInfoDTOS, roleInfoDTO);
        loginInfoDTO.setSysRoles(roleInfoDTO);
        loginInfoDTO.setSysUser(sysUserDTO);

        //获取缓存中的token，如果没有 便返回null
        String accessTokenKey = String.format(RedisConsts.S_Token_ACCESS_KEY, account, userContext);
        String token = RedisUtils.get(accessTokenKey, String.class);
        if (token == null) {
            return null;
        }
        loginInfoDTO.setAccessToken(token);
        return loginInfoDTO;
    }
}
